package com.zluyuer.ermu;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.List;

import org.apache.log4j.Logger;

import com.zluyuer.ermu.msg.Hook;
import com.zluyuer.ermu.msg.HookManager;
import com.zluyuer.ermu.msg.Message;
import com.zluyuer.ermu.msg.MessageRegistry;

public class ErmuServerThread extends Thread {

    private static Logger log = Logger.getLogger(ErmuServerThread.class);
    
    private ErmuServer server;
    private Socket socket;
    private InputStreamReader reader;
    private PrintWriter writer;
    private int readBufferSize = 4096;
    
    
    public ErmuServerThread(Socket socket, ErmuServer server) {
        this.socket = socket;
        this.server = server;
    }
    
    @Override
    public void run() {
        work();
        close();
    }
    
    public void close() {
        try {
            log.info("Closing ErmuServerThread...");
            
            socket.close();
            server.unregisterThread(getName());
            
            log.info("ErmuServerThread closed successfully");
        } catch (IOException e) {}
    }
    
    private void work() {
        log.info(getName() + " listening messages...");
        
        while (true) {   
            Message msg = null;
            try {
                reader = ErmuHelper.getSocketReader(socket);
                writer = ErmuHelper.getSocketPrintWriter(socket);
                
                boolean isConnectionReset = false;
                String xml = "";
                try{
                    xml = receiveMessage();
                } catch (IOException e) {
                    if (e.getMessage().equals("Connection reset")) {
                        isConnectionReset = true;
                    }
                }
                if (xml.length() == 0) {
                    if (isConnectionReset || !writer.checkError()) {
                        log.info("Client break down connection");
                        break;
                    } else {
                        continue;
                    }
                }
                
                msg = MessageRegistry.parse(xml);
                log.debug("Received msg: " + msg.getCode() + "-" + msg.getId());
                
                HookManager hooks = HookManager.getInstance();
                List<Hook> preHooks = hooks.getPreHooks(msg.getMsgCode());
                executeHooks(preHooks, msg, "pre");
                
                MessageRegistry.execute(msg);
    
                List<Hook> postHooks = hooks.getPostHooks(msg.getMsgCode());
                executeHooks(postHooks, msg, "post");
                
            } catch (Exception e) {
                String message = (msg==null) ? "Error" : msg.getCode() + "-" + msg.getId() + " error";
                log.error(message + ": " + e.getMessage());
                e.printStackTrace();
            }
        }
    }
    
    private String receiveMessage() throws IOException {
        StringBuilder sb = new StringBuilder();
        char[] buffer = new char[readBufferSize];
        char lastChar = '\0';  
        int size = 0;
        
        while ((size = reader.read(buffer)) >= 0) {
            lastChar = buffer[size - 1];
            if (lastChar == '\0') {
                sb.append(buffer, 0, size - 1);
                break;
            } else {
                sb.append(buffer, 0, size);
            }
        }
        
        return sb.toString();
    }
    
    private void executeHooks(List<Hook> hooks, Message msg, String type) throws Exception {
        for (Hook hook : hooks) {
            try {
                hook.execute(msg);
            } catch (Exception e) {
                throw new Exception(getName() + " error execute " + type+ " hook " + 
                        hook.getName() + ": " + e.getMessage());
            }
        }
    }
    
    public void setReadBufferSize(int readBufferSize) {
        this.readBufferSize = readBufferSize;
    }
}
